The Developer Fastlane

« 365 days to become a developer » challenge

PHP OOP: PHP Documentor

November 30, 2020

Lesson

Purpose

PHP Doc allows to comment quickly and in a standard way the Structure Element of our code. This will be usefull for team working and to understand quickly our code if we wrote it weeks or months ago (and therefore don't remember it's structure).
It is well known and supported by the majority of services and developper

How to use PHP Doc ?

  1. We chosed the plugin called PHPDoc Comment by Rex Shi, available on VisualStudioCode.
  2. Once installed, select any structural code element (namespace, require/include, class, interface,function, method, property, constant, variable, trait)
  3. Right click and select "Add PHPDoc comment" or tap command + shift + i
  4. This will generate a standard auto-generated comment. Modify it to fit your needs.

General DockBloc syntax and parts

Code
/**
 * This is the summary for a DocBlock.
 *
 * This is the description for a DocBlock. This text may contain
 * multiple lines and even some _markdown_.
 *
 * * Markdown style lists function too
 * * Just try this out once
 *
 * The section after the description contains the tags; which provide
 * structured meta-data concerning the given element.
 *
 * @author  Edouard Proust <edouardproust@gmail.com>
 *
 * @since 1.0
 *
 * @param int    $example  This is an example function/method parameter description.
 * @param string $example2 This is a second example.
 */

Reference links

  • Structure of a DocBlock: List of parts it contains and what there meant for / how to complete them properly
    • Summary: a one-liner which globally states the function of the documented element.
    • Description: an extended description of the function of the documented element; may contain markup and inline tags.
    • Tags
  • TAGS list
    • @param, @return, @throws, @source, @example,...
  • TYPES list
    • Primitives: native PHP types
      • string, int, float, bool, array, resource, null, callable
    • Keywords: not native to PHP but usefull
      • mixed, void, object, false, true, self, static, $this
    • Arrays: precise the type of element inside the array
      • DateTime[], string[],...
    • Multiple types combined: using the | separator between types

Exercise

Let's use the previous informations to add comments to a file that was created for this exercise.

OpenWeather.php

Code
<?php 

/**
 * Manage the OpenWeather API
 * 
 * @author  Edouard Proust <edouardproust@gmail.com>
 * 
 * @var string ICON_SIZE (constant) Define the size of the weather icon ('1x', '2x' or '4x')
 * @var string UNITS (constant) Define temparature unit ('default' = kelvin, 'metric' = Celsius, 'imperial' = Fahrenheit)
 * @var string $api_key The API key provided in OpenWeather account > My API keys
 * @var int $timeout cURL timeout delay. Value is defined in weather.php
 */ 
class OpenWeather {

    const ICON_SIZE = '2x';
    const UNITS = 'metric';
    private $api_key;
    private $timeout;
        
    /**
     * Class constructor
     * 
     * @param string $apiKey The full API key string
     * @param  mixed $timeOut Timeout allowed for the API to run
     * @return void
     */ 
    public function __construct(string $apiKey, int $timeOut)
    {
        $this->api_key = $apiKey;
        $this->timeout = $timeOut;
    }
       
    /**
     * Get the weather data for current moment
     * 
     * @param array $coordinates [latitude, longitude]
     * @return array [(int)'temp', (string)'description' , (string)'icon', (DateTime)'time' ]
     *   
    public function getCurrent(array $coordinates): ?array
    {
        $result = [];
        $data = $this->callAPI($coordinates);
        if ($data !== null && isset($data["current"])) {
            $result = $this->getResult($data["current"], $data["timezone"]);
        }
        return $result;
    }
       
    /**
     * Get the weather data for the last hours
     * 
     * @param array $coordinates [latitude, longitude]
     * @return array [(int)'temp', (string)'description' , (string)'icon', (DateTime)'time' ]
     * 
     * From time to time API returns data for a variable number of hours (json array's size is varying).
     * This issue comes from OpenWeather, not my code
     */ 
    public function getLastHours(array $coordinates): array
    {
        $result = [];
        $data = $this->callAPI($coordinates);
        if ($data !== null && isset($data["hourly"])) {
            foreach ($data["hourly"] as $hour) {
                $result[] = $this->getResult($hour, $data["timezone"]);
            }
        }
        return $result;
    }
       
    /**
     * Format the location string to get a proper City name + Country code
     * 
     * @param  string $location 'location-name_country-code' (example: 'paris_fr', 'new-york_us')
     * @return  array [(string)city name, (string)country code]
     */ 
    public static function getName(string $location): array
    {
        $parts = explode('_', $location);
        $loc_array['city'] = implode( '-', array_map( 'ucfirst', explode('-', $parts[0]) ) );
        $loc_array['country'] = strtoupper($parts[1]);
        return $loc_array;
    }
        
    /**
     * Call OpenWeather API
     * 
     * @param array $coordinates [latitude, longitude]
     * @return null|array json_decode[]
     *  
     * @throws CurlException cURL error (timeout,...)
     * @throws HTTPException API error returning any HTTP code (401, 404,...)
     */ 
    private function callAPI(array $coordinates): ?array
    {
        $time = time()-1;
        $latitude = $coordinates[0];
        $longitude = $coordinates[1];
        $unit = self::UNITS;
        $curl = curl_init("https://api.openweathermap.org/data/2.5/onecall/timemachine?lat={$latitude}&lon={$longitude}&dt=$time&units={$unit}&appid={$this->api_key}");
        curl_setopt_array($curl, [
            CURLOPT_CAINFO          => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'certificates' . DIRECTORY_SEPARATOR . 'openweather.cer',
            CURLOPT_RETURNTRANSFER  => true,
            CURLOPT_TIMEOUT_MS      => $this->timeout
        ]);
        $data = curl_exec($curl);
        if ($data === false) {
            throw new CurlException($curl);
        }
        $code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        if ($code !== 200) {
            curl_close($curl);
            throw new HTTPException($data);
        }
        curl_close($curl);
        return json_decode($data, true);
    }
        
    /**
     * Formats weather data and returns it into an array 
     * 
     * @param  mixed $data_when ($data["current"], $data["hourly"],...)
     * @param  mixed $timezone ($data["timezone"],...)
     * @return array [(int)'temp', (string)'description' , (string)'icon', (DateTime)'time' ]
     */ 
    private function getResult($data_when, $timezone): array
    {
        if (self::ICON_SIZE === '2x' || self::ICON_SIZE === '4x') {
            $icon_size = '@' . self::ICON_SIZE;
        } else {
            $icon_size = '';
        }
        return [
            'temp'          => $data_when["temp"] . '°C',
            'description'   => ucfirst($data_when["weather"][0]["description"]),
            'icon'          => 'http://openweathermap.org/img/wn/' . $data_when["weather"][0]["icon"] . $icon_size . '.png',
            'time'          => $this->getTimeFormated($data_when["dt"], $timezone),
        ];
    }
             
    /**
     * Formats several datetime strings and store them into an array
     * 
     * @param  mixed $timestamp (time(),...)
     * @param  mixed $timezone ($data["timezone"], 'Europe/Paris',...)
     * @return array string[]
     */ 
    private function getTimeFormated($timestamp, $timezone): array
    {
        $date = new DateTime("@" . $timestamp);
        $date->setTimeZone(new DateTimeZone($timezone));
        return  [
            'all' => $date->format('M d g:i a'),
            'date' => $date->format('M d'),
            'hour' => $date->format('g a'),
        ];
    }

}
© 2020 - Edouard Proust | The Developer Fastlane